
#ifndef LOD_TEST_DISP_H
#define LOD_TEST_DISP_H


#include "disp.h"

#include "../types.h"
#include "../light/lighting.h"
#include "../base/material.h"
#include "../base/point3.h"
#include "../jeff_camera.h"
#include "../proc_wrappers.h"

#include "../jeff/mesh.h"
#include "../nat_timer.h"

#include "box.h"

#include <sys/time.h>
#include <string>
#include <sstream>

#include <GL/glut.h>

#include <iostream>
using std::cout;
using std::flush;
using std::endl;


class test_disp : public disp
{
    public:
    test_disp(void* font, const GLfloat* menu_color4, const GLfloat* act_menu_color4,
                int Width, int Height)//, double Fovy)
     : disp(font, menu_color4, act_menu_color4, test_disp::text, 
            test_disp::yinc_text, test_disp::xoffset_text, 
            test_disp::show_text, test_disp::menu_pos,
            test_disp::text_count,
            test_disp::help_str, test_disp::help_count),
       width(Width),
       height(Height),
       //fovy(Fovy),
       typing(false),
       ob_loaded(false),
       //use_vbo(false),
       //polygon_mode(true),
       //draw_balls(true),
       default_mat(DEFAULT_MAT_DIFF, DEFAULT_MAT_SPEC),
       li(0), // no lights but we can manipulate lighting
       distance_condition(true),
       velocity_condition(true),
       fps_counter(10)
    {
        set_act_menu_pos(START_ACT_MENU_POS); 
        //set_show_help(HELP_SHOWED_ON_START); 
        show_help=false;
    }

    virtual ~test_disp();
    

    virtual void display(int width, int height)
    {
        if(ob_loaded)
        {
            if(show_help==false)
            {
                if(display_ob)
                {
                    struct timeval stamp;
                    if(gettimeofday(&stamp,NULL)) // should never happen
                        cout<<"error: failed to get the time of day"<<endl<<flush;
                    else if(pause==false)
                    {
                        double t = stamp.tv_sec + stamp.tv_usec/1e6;
                        
                        //static double tstart=t;
                        //static int count=0;
                        //if(t-last_tick_time>=1.0/ticks_per_sec) {
                        //    last_tick_time=t;//-(t-last_tick_time);
                            //count++;
                        //    ballbox.tick();
                        //}
                        //if(t-tstart>5) {
                        //    tstart=t;
                        //    cout<<count<<endl<<flush;
                        //    count=0;
                        //}
                        ballbox.tick(t-last_tick_time);
                        last_tick_time=t;
                    }

                    if(polygon_mode)
                        glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
                    else
                        glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );

                    if(illuminated) {
                        li.enable();
                        //default_mat.set();
                    }
                    else {
                        li.disable();
                        grey();
                    }

                    glMatrixMode(GL_MODELVIEW);
                    glLoadIdentity();
                    gluLookAt(0,0,jcam.get_dist(), 0,0,0, 0,1,0);
                    glRotatef(jcam.get_elevation(),1,0,0);
                    glRotatef(jcam.get_azimuth(),0,1,0);
                    //cout<<jcam.get_dist()<<endl<<flush;
                    //cout<<jcam.get_azimuth()<<endl<<flush;
                    //cout<<jcam.get_elevation()<<endl<<flush;
                    point3<float> eye = jcam.get_cartesian_co();
                    //cout<<p.x<<"  "<<p.y<<"  "<<p.z<<endl<<flush;

                    int center=10;
                    glTranslatef(0,0,center);
                    
                    int end=ballbox.num_balls();

                    clear_histogram();
                    if(use_lod) {
                        for(int i=0; i<end; i++) {
                            point3<float> p = ballbox.get_ball_pos(i);
                            float rad = ballbox.get_ball_radius(i);
                            unsigned lod;

                            glPushMatrix();
                            glTranslatef(p.x,p.y,p.z);
                            p.z+=10;
                            
                            if(draw_balls)
                                glutWireSphere(rad,8,8);
                                
                            glScalef(rad,rad,rad);

                            // here we decide which LOD to use
                            lod = check_distance_condition(eye,p);
                            histogram[lod]++;
                            display_jmesh(meshes[lod],lod);
                            
                            glPopMatrix();
                        }
                    }
                    else {
                        for(int i=0; i<end; i++) {
                            point3<float> p = ballbox.get_ball_pos(i);
                            float rad = ballbox.get_ball_radius(i);

                            glPushMatrix();
                            glTranslatef(p.x,p.y,p.z);
                            p.z+=10;
                            
                            if(draw_balls)
                                glutWireSphere(rad,8,8);
                                
                            glScalef(rad,rad,rad);
                            display_jmesh(meshes[0],0);
                            
                            glPopMatrix();
                        }
                        histogram[0]=end;
                    }

                    display_pyramid();

                    li.disable();
                    display_histogram();
                    if(use_lod)
                        quick_draw_string(width,height,"lods on", width-100,40);
                    else
                        quick_draw_string(width,height,"lods off", width-100,40);
                    quick_draw_string(width,height,("fps:  "+get_fps_str()).c_str(), width-100,20);
                } // display_ob==true

                li.disable();
                quick_draw_string(width,height,("File: "+ob_name).c_str(),width/2,20);
            } // show_help==false
            else {
                li.disable();
                disp_help(width,height);
            }
        }

        // do any time
        li.disable();
        disp_text(width, height);
    }

    virtual disp* keyboard(unsigned char key, int x, int y)
    {
        disp* menu = this;
        if(typing)
        {
            switch(key) {
                //case '\n': 
                //    // in case "\r\n" is generated for enter key
                //    break;
                // strange observation: glut gives just '\r' as a newline character
                // under Linux
                case '\r':
                    this->enter_key();
                    break;
                case 27: // escape
                    menu = escape_key();
                    break;
                case '\b':
                    backspace_key();
                    break;
                default:
                    key_typed(key);
            }
        }
        else // typing==false
        {
            if(key=='\r')
                // strange observation: glut gives just '\r' as a newline character
                // under Linux
                enter_key();
            else if(key==27)//escape
                menu = escape_key();
            else if(key=='\b')
                backspace_key();
            else if(ob_loaded)
            {
                switch(key) {
                    case 'l': // turn on or off the lighting
                        illuminated = !illuminated;
                        break;
                    case 'd': // display / don't display the meshes
                        display_ob = !display_ob;
                        if(display_ob)
                            display_on();
                        else
                            display_off();
                        break;
                    case 'S':
                        li.swap_shading(); // switch between flat/smooth shading
                        break;
                    case 'f':
                        polygon_mode = !polygon_mode;
                        break;

                    case 'p':
                        set_pause(!pause);
                        break;

                    case ']':
                                              // l,r   b,t   b,f  vmin,vmax  radmin,radmax
                                              // -4,4, -3,3, -20,0, 1,2, 1,2);
                        ballbox.add_random_ball(-1,1, -1,1, -19,-1, 1,5, 0.5f,2);
                        break;
                    case '[':
                        ballbox.del_random_ball();
                        break;

                    case 'b':
                        draw_balls =! draw_balls;
                        break;
                    case 'u':
                        use_lod = !use_lod;
                        break;

                    case 'h':
                        show_help = !show_help;
                        if(show_help)
                            display_off();
                        else
                            display_on();
                        break;

                    case 'n':
                        cout<<"objects: "<<ballbox.num_balls()<<endl;
                        if(ballbox.num_balls() > 0) {
                            cout<<"triangles: "
                                << ballbox.num_balls() * meshes[0]->getNumTriangles()
                                <<endl;
                        }
                        cout<<flush;
                        break;
                    case 'v':
                        if(isExtensionSupported("GL_ARB_vertex_buffer_object")) {
                            use_vbo=!use_vbo;
                            if(use_vbo)
                                cout<<"vbo on"<<endl<<flush;
                            else
                                cout<<"vbo off"<<endl<<flush;
                        }
                        else
                            cout<<"vbo not supported"<<endl<<flush;
                        break;
                    case 'c':
                        if(glIsEnabled(GL_CULL_FACE))
                            glDisable(GL_CULL_FACE);
                        else
                            glEnable(GL_CULL_FACE);
                        break;

                    default:
                        ;
                }
            }
        }
        return menu;
    }

    virtual void mouse(int button, int state, int x, int y)
    {
        if(button==GLUT_LEFT_BUTTON) {
            lmb_state = state;
            if(state==GLUT_DOWN) {
                rmb_state = GLUT_UP;    // restrict that only one button is pressed
            }
        }
        else if(button==GLUT_RIGHT_BUTTON) {
            rmb_state = state;
            if(state==GLUT_DOWN)
                lmb_state = GLUT_UP;
        }
    }

    
    virtual void motion(int x, int y)
    {
        jcam.mouseMotion(x,y,width,height, lmb_state==GLUT_DOWN, rmb_state==GLUT_DOWN);
    }

    
    virtual void passive_motion(int x, int y)
    {
        jcam.mouseMotion(x,y,width,height, false, false);
    }

    virtual void reshape(int width, int height)
    {
        if(ob_loaded==false) {
            set_xstart_text(int(width * 1/5.0));
            set_ystart_text(int(height * 1/2.0));
        }
        else {
            set_xstart_text(15);
            set_ystart_text(height-30);
        }
        // store the updated values of width and height
        if(width!=this->width || height!=this->height) {
            this->width = width;
            this->height = height;
            init(); // update the projection matrix
        }
    }

    virtual void init()
    {
        glViewport(0, 0, (GLsizei) width, (GLsizei) height);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluPerspective(60, (GLdouble) width/(GLdouble) height, 
                        0.1, 40.0); // todo should use dynamic values
        initOpenGL();
        visibility(GLUT_VISIBLE);
    }

    virtual void deinit()
    {
        visibility(GLUT_NOT_VISIBLE);
    }

    virtual void visibility(int vis)
    {
        if(vis==GLUT_VISIBLE && ob_loaded)
            display_on();
        else 
            // todo: the function gets called, but the idle function is still 
            // generated when the window gets hidden; don't know yet why
            display_off();
    }


    private:

    void initOpenGL()
    {
	glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
	glClearDepth(1.0f);
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

        li.disable_all();
        //todo probably it's necessary to bring back the light's parameters
        // (they might have changed in another screen)
	// Enable lights and lighting
	glEnable(GL_LIGHT0); // default value is (1.0, 1.0, 1.0, 1.0)
	glEnable(GL_LIGHTING);

        
	glEnable(GL_CULL_FACE); // backface culling

        //comp_type diff[3] = {0.8, 0.8, 0.8}; // grey
        //comp_type spec[3] = {0, 0, 0};
        //material<comp_type> m(diff,spec);
        //m.shininess=128;
        //m.set();
        default_mat.set();

	//li.set_smooth_shading(); // Gouraud shading
        li.set_flat_shading();
    }

    disp* escape_key()
    {
        disp* menu = this;
        if(typing) {
            typing = false;
            typed.clear();
            
            stop_typing();
        }
        else
            menu = change_menu(false); // go back
        return menu;
    }

    void backspace_key()
    {
        //it might be necessary to add the dependence on ob_loaded (see gen_disp.h)
        if(typing && typed.length()>0) {
            typed = typed.substr(0, typed.length()-1);
            std::string s = std::string(text[1]) + typed;
            change_text(1, s.c_str());
        }
    }

    void enter_key()
    {
        //it might be necessary to add the dependence on ob_loaded
        typing = !typing;
        if(typing==true) 
        {
            start_typing();
        }
        else// typing==false 
        {
            stop_typing();
            if(typed.length()>0) 
                load_multi_ply(typed);
                //todo load_ob(typed);
            typed.clear();
        }
    }

    void start_typing()
    {
        //it might be necessary to add the dependence on ob_loaded
        menu_pos_visible(0, false);
        menu_pos_visible(1, true);
        menu_pos_active(0, false);
        menu_pos_active(1, true);
        set_act_menu_pos(1);
    }

    void stop_typing()
    {
        //it might be necessary to add the dependence on ob_loaded
        menu_pos_visible(0, true);
        menu_pos_visible(1, false);
        menu_pos_active(0, true);
        menu_pos_active(1, false);
        change_text(1, text[1]);
        set_act_menu_pos(0);
    }

    void key_typed(unsigned char key)
    {
        //it might be necessary to add the dependence on ob_loaded
        typed += key;
        std::string s = std::string(text[1]) + typed;
        change_text(1, s.c_str());
    }

    void load_multi_ply(std::string filename);

    void display_on()
    {
        struct timeval stamp;
        if(gettimeofday(&stamp,NULL)) // should never happen
            cout<<"error: failed to get time of the day"<<endl<<flush;
        else {
            last_tick_time = stamp.tv_sec + stamp.tv_usec/1e6;
            glutIdleFunc(idle_proc_wrapper);
        }
    }

    void display_off()
    {
        glutIdleFunc(NULL);
    }

    void set_pause(bool b)
    {
        if(b)
            pause=true;
        else {
            display_on();
            pause=false;
        }
    }

    void set_box()
    {
        pts.clear();
        pts.push_back(point3<float>(-4,3,0));
        pts.push_back(point3<float>(4,3,0));
        pts.push_back(point3<float>(4,-3,0));
        pts.push_back(point3<float>(-4,-3,0));

        pts.push_back(point3<float>(-2,1.5,-20));
        pts.push_back(point3<float>(2,1.5,-20));
        pts.push_back(point3<float>(2,-1.5,-20));
        pts.push_back(point3<float>(-2,-1.5,-20));
        
        ballbox.clear();
        add_planes2box();
        add_balls2box();
    }

    void add_planes2box()
    {
        ballbox.add_plane(1/4.0f, 0, 1/40.0f, 1); // left
        ballbox.add_plane(-1/4.0f, 0, 1/40.0f, 1); // right
        ballbox.add_plane(0,0,-1, 0); // front
        ballbox.add_plane(0,0,1, 20); // back
        ballbox.add_plane(0, -1/3.0f, 1/40.0f, 1); // top
        ballbox.add_plane(0, 1/3.0f, 1/40.0f, 1); // bottom
    }

    void add_balls2box()
    {
        //ballbox.add_ball(0,0,1, 0,1,0);
        int num=3;
        for(int i=0; i<num; i++)
            ballbox.add_random_ball(-1,1, -1,1, -19,-1, 1,3, 1,2);
    }

    void grey()
    {
        // Make everything grey
        glColor3ub(128, 128, 128);
    }

    void display_pyramid()
    {
        float diffleft[] = {0.3,0.9,0.2};
        float diffright[] = {0.2,0.3,0.9};
        float diffback[] = {0.3,0.15,0.15};
        float difffront[] = {0.8,0.9,0.9};
        material<float> mleft(diffleft); // material for left side
        material<float> mright(diffright); // material for right side
        material<float> mback(diffback); // for back
        material<float> mfront(difffront);
        int numl=lup.size(); //==ldown.size()
        int numr=rup.size(); //==rdown.size()

        set_poly_mode(false,false); // wireframe
        
        if(illuminated)
            li.enable();
        

        mback.set(true,true,illuminated);
        glBegin(GL_TRIANGLES);
            // back face
            glNormal3f(0,0,-1);
            glVertex3fv(&pts[5].x);
            glVertex3fv(&pts[6].x);
            glVertex3fv(&pts[7].x);

            glVertex3fv(&pts[7].x);
            glVertex3fv(&pts[4].x);
            glVertex3fv(&pts[5].x);

            glNormal3f(0,0,1);
            glVertex3fv(&pts[7].x);
            glVertex3fv(&pts[6].x);
            glVertex3fv(&pts[5].x);

            glVertex3fv(&pts[5].x);
            glVertex3fv(&pts[4].x);
            glVertex3fv(&pts[7].x);
        glEnd();

        
        mleft.set(true,true,illuminated);
        glBegin(GL_TRIANGLES);
            // left face
            glNormal3f(-0.25f, 0, -1.0f/40.0f);
            for(int i=1; i<numl; i++) {
                glVertex3fv(&lup[i].x);
                glVertex3fv(&ldown[i].x);
                glVertex3fv(&ldown[i-1].x);

                glVertex3fv(&ldown[i-1].x);
                glVertex3fv(&lup[i-1].x);
                glVertex3fv(&lup[i].x);
            }

            glNormal3f(0.25f, 0, 1.0f/40.0f);
            for(int i=1; i<numl; i++) {
                glVertex3fv(&ldown[i-1].x);
                glVertex3fv(&ldown[i].x);
                glVertex3fv(&lup[i].x);

                glVertex3fv(&lup[i].x);
                glVertex3fv(&lup[i-1].x);
                glVertex3fv(&ldown[i-1].x);
            }
        glEnd();

        
        mright.set(true,true,illuminated);
        glBegin(GL_TRIANGLES);
            // right face
            //glNormal3f(1,0,0);
            glNormal3f(1/4.0f, 0, -1/40.0f);
            for(int i=0; i<numr-1; i++) {
                glVertex3fv(&rup[i].x);
                glVertex3fv(&rdown[i].x);
                glVertex3fv(&rdown[i+1].x);

                glVertex3fv(&rdown[i+1].x);
                glVertex3fv(&rup[i+1].x);
                glVertex3fv(&rup[i].x);
            }
            
            glNormal3f(-1,0,0);
            for(int i=1; i<numr; i++) {
                glVertex3fv(&rup[i].x);
                glVertex3fv(&rdown[i].x);
                glVertex3fv(&rdown[i-1].x);

                glVertex3fv(&rdown[i-1].x);
                glVertex3fv(&rup[i-1].x);
                glVertex3fv(&rup[i].x);
            }
        glEnd();

        

        int min = numl<numr ? numl : numr;

        // use left face's material for the top face
        mleft.set(true,true,illuminated);
        glBegin(GL_QUADS);
            // top face
            glNormal3f(0,1,0);
            for(int i=1; i<min; i++) {
                glVertex3fv(&rup[i].x);
                glVertex3fv(&lup[i].x);
                glVertex3fv(&lup[i-1].x);
                glVertex3fv(&rup[i-1].x);
            }

            glNormal3f(0,-1,0);
            for(int i=1; i<min; i++) {
                glVertex3fv(&rup[i-1].x);
                glVertex3fv(&lup[i-1].x);
                glVertex3fv(&lup[i].x);
                glVertex3fv(&rup[i].x);
            }
        glEnd();

        // use right face's material for the bottom face
        mright.set(true,true,illuminated);
        glBegin(GL_QUADS);
            // bottom face
            glNormal3f(0,-1,0);
            for(int i=1; i<min; i++) {
                glVertex3fv(&ldown[i].x);
                glVertex3fv(&rdown[i].x);
                glVertex3fv(&rdown[i-1].x);
                glVertex3fv(&ldown[i-1].x);
            }
            
            glNormal3f(0,1,0);
            for(int i=1; i<min; i++) {
                glVertex3fv(&ldown[i-1].x);
                glVertex3fv(&rdown[i-1].x);
                glVertex3fv(&rdown[i].x);
                glVertex3fv(&ldown[i].x);
            }
        glEnd();

        mfront.set(true,true,illuminated);
        //glBegin(GL_TRIANGLES);
        glBegin(GL_QUADS);
            // front face
            glNormal3f(0,0,1);
            glVertex3fv(&pts[0].x);
            glVertex3fv(&pts[3].x);
            glVertex3fv(&pts[2].x);

            //glVertex3fv(&pts[2].x);
            glVertex3fv(&pts[1].x);
            //glVertex3fv(&pts[0].x);

            glNormal3f(0,0,-1);
            glVertex3fv(&pts[2].x);
            glVertex3fv(&pts[3].x);
            glVertex3fv(&pts[0].x);

            //glVertex3fv(&pts[0].x);
            glVertex3fv(&pts[1].x);
            //glVertex3fv(&pts[2].x);
        glEnd();
    }


    void generate_pyramid()
    {
        // generate points along the long edges (deep to z-dimension)
        assert(pts.size()==8);
        int numl=5;
        int numr=3;
        float fl;
        float fr;
        float incl=1.0f/numl;
        float incr=1.0f/numr;

        vector3<float> vup;
        vector3<float> vdown;
        
        lup.clear();
        ldown.clear();
        rup.clear();
        rdown.clear();
        

        vup.x=pts[4].x-pts[0].x;
        vup.y=pts[4].y-pts[0].y;
        vup.z=pts[4].z-pts[0].z;
        vdown.x=pts[7].x-pts[3].x;
        vdown.y=pts[7].y-pts[3].y;
        vdown.z=pts[7].z-pts[3].z;

        lup.push_back(pts[0]);
        ldown.push_back(pts[3]);

        fl=incl;
        
        for(int i=1; i<numl; i++) {
            lup.push_back(point3<float>(pts[0].x+fl*vup.x,
                                        pts[0].y+fl*vup.y,
                                        pts[0].z+fl*vup.z));

            ldown.push_back(point3<float>(pts[3].x+fl*vdown.x,
                                          pts[3].y+fl*vdown.y,
                                          pts[3].z+fl*vdown.z));
            fl+=incl;
        }

        lup.push_back(pts[4]);
        ldown.push_back(pts[7]);


        vup.x=pts[5].x-pts[1].x;
        vup.y=pts[5].y-pts[1].y;
        vup.z=pts[5].z-pts[1].z;
        vdown.x=pts[6].x-pts[2].x;
        vdown.y=pts[6].y-pts[2].y;
        vdown.z=pts[6].z-pts[2].z;

        rup.push_back(pts[1]);
        rdown.push_back(pts[2]);

        fr=incr;

        for(int i=1; i<numr; i++) {
            rup.push_back(point3<float>(pts[1].x+fr*vup.x,
                                        pts[1].y+fr*vup.y,
                                        pts[1].z+fr*vup.z));

            rdown.push_back(point3<float>(pts[2].x+fr*vdown.x,
                                          pts[2].y+fr*vdown.y,
                                          pts[2].z+fr*vdown.z));
            fr+=incr;
        }

        rup.push_back(pts[5]);
        rdown.push_back(pts[6]);
    }

    void clear_pyramid()
    {
        lup.clear();
        ldown.clear();
        rup.clear();
        rdown.clear();
    }

    void set_poly_mode(bool on_bool=true, bool value=true)
    {
        if(on_bool) {
            if(polygon_mode)
                glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
            else
                glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
        }
        else if(value)
            glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
        else
            glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
    }

    void display_jmesh(jeff::Mesh* mesh, int idx=0);
    

    void display_histogram()
    {
        int numb=ballbox.num_balls();
        int numl=histogram.size();

        if(numb<=0)
            return;

        float h=height/(5.0f*numb);
        float w=width/60;
        int xstart=20;
        int ystart=30;
        GLfloat redbase=0.2f;
        GLfloat redend=1.0f;
        GLfloat greenbase=1.0f;
        GLfloat greenend=0.2f;
        GLfloat redch;
        GLfloat greench;

        if(numl>1) {
            redch=(redend-redbase)/(numl-1);
            greench=(greenend-greenbase)/(numl-1);
        }
        else {
            redch=redend-redbase;
            greench=greenend-greenbase;
        }

        push_ortho(width,height);

        set_poly_mode(false,true); // filled
        glDisable(GL_DEPTH_TEST);
        glBegin(GL_QUADS);
            for(int i=0; i<numl; i++) {
                glColor3f(0.2f+i*redch,1.0f+i*greench,0.2f);
                glVertex2f(xstart+i*w,ystart);
                glVertex2f(xstart+(i+1)*w,ystart);
                glVertex2f(xstart+(i+1)*w,ystart+histogram[i]*h);
                glVertex2f(xstart+i*w,ystart+histogram[i]*h);
            }
        glEnd();
        glEnable(GL_DEPTH_TEST);

        pop_ortho();

        quick_draw_string(width,height,"0", xstart, ystart-20);
        if(numl>1)
            quick_draw_string(width,height,int2str(numl-1).c_str(), xstart+(numl-1)*w, ystart-20);
    }

    void clear_histogram()
    {
        unsigned end=histogram.size();
        for(unsigned i=0; i<end; i++)
            histogram[i]=0;
    }

    unsigned check_distance_condition(point3<float> eye, point3<float> bpos)
    {
        assert(meshes.size()>0);
        float dist = std::sqrt((eye.x-bpos.x)*(eye.x-bpos.x) + 
                               (eye.y-bpos.y)*(eye.y-bpos.y) + 
                               (eye.z-bpos.z)*(eye.z-bpos.z));
        unsigned r = (unsigned)(dist/(20.0f/meshes.size()));
        if(r>=meshes.size())
            r=meshes.size()-1;
        return r;
    }

    /*unsigned check_surface_condition(point3<float> pos, float rad)
    {
        GLfloat m[16];
        GLfloat p[4] = {pos.x, pos.y, pos.z, 1};
        GLfloat pt[4];
        glMatrixMode(GL_PROJECTION);

        glGetFloatv(GL_PROJECTION_MATRIX, m);
        pt[0] = m[0]*p[0] + m[4]*p[1] + m[8]* p[2] + m[12]*p[3];
        pt[1] = m[1]*p[0] + m[5]*p[1] + m[9]* p[2] + m[13]*p[3];
        pt[2] = m[2]*p[0] + m[6]*p[1] + m[10]*p[2] + m[14]*p[3];
        pt[3] = m[3]*p[0] + m[7]*p[1] + m[11]*p[2] + m[15]*p[3];
        cout<<pt[0]<<"  "<<pt[1]<<"  "<<pt[2]<<"  "<<pt[3]<<endl<<flush;

        glMatrixMode(GL_MODELVIEW);
    }*/
    
    std::string get_fps_str()
    {
        std::ostringstream os;
                // take one number after dot
        os << ((int)(fps_counter.GetFPS()*10))/10.0f;
        return os.str();
    }

    std::string int2str(int i)
    {
        std::ostringstream os;
        os<<i;
        return os.str();
    }

    // source: http://www.opengl.org/resources/features/OGLextensions/
    int isExtensionSupported(const char *extension)
    {
        const GLubyte *extensions = NULL;
        const GLubyte *start;
        GLubyte *where, *terminator;
        /* Extension names should not have spaces. */
        where = (GLubyte *) strchr(extension, ' ');
        if (where || *extension == '\0')
            return 0;
        extensions = glGetString(GL_EXTENSIONS);
        /* It takes a bit of care to be fool-proof about parsing the
          OpenGL extensions string. Don't be fooled by sub-strings,
          etc. */
        start = extensions;
        for (;;) {
            where = (GLubyte *) strstr((const char *) start, extension);
            if (!where)
                break;
            terminator = where + strlen(extension);
            if (where == start || *(where - 1) == ' ')
                if (*terminator == ' ' || *terminator == '\0')
                    return 1;
            start = terminator;
        }
        return 0;
    }

    void load_jmesh_to_vbo(jeff::Mesh* mesh)
    {
    }

    

    private:
    int width;
    int height;
    //double fovy;

    bool typing;
    std::string typed;

    std::string ob_name;

    bool ob_loaded;
    bool display_ob;
    bool use_vbo;
    bool illuminated;
    bool polygon_mode; // false ~= wireframe, true ~= normal
    bool pause;
    bool draw_balls;
    bool use_lod;

    material<comp_type> default_mat;

    lighting li;
    jeff_camera jcam;

    std::vector<jeff::Mesh*> meshes;
    std::vector<GLuint> vbo_ids;
    std::vector<int> histogram;

    //int ticks_per_sec;
    double last_tick_time;

    box ballbox;

    std::vector<point3<float> > pts;
    std::vector<point3<float> > lup;
    std::vector<point3<float> > ldown;
    std::vector<point3<float> > rup;
    std::vector<point3<float> > rdown;

    bool distance_condition;
    bool velocity_condition;

    natFPS fps_counter;

    static const char* text[];
    static const int yinc_text[];
    static const int xoffset_text[];
    static bool show_text[];
    static bool menu_pos[];
    static const int text_count;

    static const char* help_str[];
    static const int help_count;

    static const short START_ACT_MENU_POS;
    //static const bool HELP_SHOWED_ON_START;

    static const comp_type DEFAULT_MAT_DIFF[];
    static const comp_type DEFAULT_MAT_SPEC[];
};

#endif // LOD_TEST_DISP_H
